#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "job.h"

#define MAXARGS 128
#define MAXLINE 8192 

extern char **environ;
extern job_info *jobs;  /* job-link for all jobs, jobs[0] save fg pid */
volatile sig_atomic_t child;
sigset_t oldmask;

void parent_handler(int sig)
{
    int status;
    pid_t pid;
    job_info *job;

    if (sig == SIGINT && jobs->pid != 0)
        kill(-jobs->pid, sig);
    else if (sig == SIGTSTP && jobs->pid != 0)
        kill(-jobs->pid, sig);
    else if (sig == SIGQUIT && jobs->pid != 0)
        kill(-jobs->pid, sig);
    else if (sig == SIGCHLD) {
        while ((pid = waitpid(-1, &status, WNOHANG|WUNTRACED|WCONTINUED)) > 0) { 
            if (WIFEXITED(status)) {
                if (pid == jobs->pid) {
                    jobs->pid = 0;
                    child = 1;
                }
                job = find_job(0, pid);
                /*printf("Job [%d] %s terminated with exit at %d\n", job->jid, 
                    job->cmdline, WEXITSTATUS(status));*/
                del_job(pid);
            }
            else if (WIFSIGNALED(status)) {
                if (pid == jobs->pid) {
                    jobs->pid = 0;
                    child = 1;
                }
                job = find_job(0, pid);
                printf("Job [%d] %s termminated by %s\n", job->jid, job->cmdline,
                    (char *)strsignal(WTERMSIG(status)));
                del_job(pid);
            }
            else if (WIFSTOPPED(status)) {
                if (pid == jobs->pid) {
                    jobs->pid = 0;
                    child = 1;
                }
                job = set_job(pid, STOP, NOT);
                printf("Job [%d] %s stopped by %s\n", job->jid, job->cmdline,
                    (char *)strsignal(WSTOPSIG(status)));
            }
            else if (WIFCONTINUED(status)) {
                if (pid == jobs->pid) 
                    child = 1;
                else
                    set_job(pid, RUN, BG);
            }
        }
    }
}

/* if first arg is a builtin command, run it and return true */
int builtin_command(char **argv)
{   
    job_info *job;
    pid_t pid;
    int jid;

    /* quit command */
    if (!strcmp(argv[0], "quit") || !strcmp(argv[0], "exit"))   
        exit(0);

    if (!strcmp(argv[0], "jobs")) { /* jobs command, show all bg jobs */
        print_jobs();
        return 1;
    }

    if (!strcmp(argv[0], "bg")) {   /* bg command, continue in the bg */
        if (argv[1] == NULL)
            pid = 0;
        else if (argv[1][0] == '%')
            jid = atoi(&argv[1][1]);
        else
            pid = atoi(argv[1]);

        job = find_job(jid, pid); 
        if (job == NULL)
            printf("No such process!\n");
        else if (strcmp(job->status, RUN)) {
            printf("Job [%d] %s %s\n", job->jid, job->cmdline, RUN);
            kill(job->pid, SIGCONT);
        }
        else
            printf("job [%d] %s has been %s in bg\n", job->jid, 
                job->cmdline, RUN);
        return 1;
    }

    if (!strcmp(argv[0], "fg")) {   /* fg command, continue in the fg */
        if (argv[1] == NULL)
            pid = 0;
        else if (argv[1][0] == '%')
            jid = atoi(&argv[1][1]);
        else
            pid = atoi(argv[1]);

        job = find_job(jid, pid); 
        if (job == NULL)
            printf("No such process!\n");
        else {
            job = set_job(job->pid, RUN, FG);
            printf("Job [%d] %s %s\n", job->jid, job->cmdline, job->status);
            jobs->pid = job->pid;
            kill(job->pid, SIGCONT);
            while (child == 0)
                sigsuspend(&oldmask);
            child = 0;
        }
        return 1;
    }

    if (!strcmp(argv[0], "&"))      /* ignore singleton & */
        return 1;

    return 0;
}

/* parse the command line and bild the argv array */
int parseline(char *cmdline, char **argv)
{
    char *delim;                    /* points to first space delimiter */
    int argc;                       /* number of args */
    int bg;                         /* background job? */
    char *buf = cmdline;

    while (*buf && (*buf == ' '))
        buf++;

    argc = 0;
    while (delim = strchr(buf, ' '))
    {
        argv[argc++] = buf;
        *delim = '\0';
        buf = delim + 1;
        while (*buf && (*buf == ' '))
            buf++;
    }
    if (*buf != '\0')
        argv[argc++] =  buf;
    argv[argc] = NULL;

    if (argc == 0)
        return 1;

    /* should the job run in the background? */
    if ((bg = (*argv[argc-1] == '&')) != 0)
        argv[--argc] = NULL;

    return bg;
}

/* evaluate a command line */
void eval(char *cmdline)
{
    char *argv[MAXARGS];            /* argv for execve() */
    int bg;                         /* should the job run in bg or fg */
    pid_t pid;                      /* process id */
    char *cmdcpy;
    sigset_t mask;
    job_info *job;

    /* replace trailing '\n' with NUL */
    cmdline[strlen(cmdline)-1] = '\0';     
    cmdcpy = malloc(strlen(cmdline) + 1);
    strcpy(cmdcpy, cmdline);

    bg = parseline(cmdline, argv);
    if (argv[0] == NULL)            /* ignore empty line */
        return;

    if (!builtin_command(argv)) {
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, &oldmask);
    if ((pid = fork()) == 0) {  /* child process job */
        setpgid(0, 0);          /* set the pid to gid */
        signal(SIGINT, SIG_DFL);
        signal(SIGTSTP, SIG_DFL);
        signal(SIGQUIT, SIG_DFL);
        signal(SIGCHLD, SIG_DFL);
        sigprocmask(SIG_UNBLOCK, &mask, NULL);

        if (execvp(argv[0], argv) < 0) {
            printf("%s Command not found.\n", argv[0]);
            exit(0);
        }
    }

    add_job(pid, RUN, cmdcpy);
    if (!bg) {      /* parents wait for foreground job to terminate */
        jobs->pid = pid;
        while (child == 0)
            sigsuspend(&oldmask);
        child = 0;
    }
    else {
        job = find_job(0, pid);
        printf("Job [%d] %d\n", job->jid, job->pid);
    }
    }
    sigprocmask(SIG_UNBLOCK, &mask, NULL);

    return;   
}

int main(int argc, char *argv[], char *envp[])
{
    char cmdline[MAXLINE];

    signal(SIGINT, parent_handler);
    signal(SIGTSTP, parent_handler);
    signal(SIGQUIT, parent_handler);
    signal(SIGCHLD, parent_handler);

    init_jobs();
    while(1) {
        printf("xsh > ");
        fgets(cmdline, MAXLINE, stdin);
        eval(cmdline);
    }
    free_jobs();
    
    return 0;
}
